// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/signin/profile_colors_util.h"

#include "base/containers/contains.h"
#include "base/rand_util.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/new_tab_page/chrome_colors/generated_colors_info.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/themes/browser_theme_pack.h"
#include "chrome/browser/themes/custom_theme_supplier.h"
#include "chrome/browser/themes/theme_properties.h"
#include "ui/gfx/color_utils.h"
#include "ui/native_theme/native_theme.h"

namespace {

// Minimum saturation for a color to be autoselected (as picking 'colorful'
// colors is needed to distinguish colors from each other).
constexpr double kMinimumSaturation = 0.25;

// Maximum diff in lightness of an autoselected color to the color of the
// current profile (so that the interception UI does not look bad).
constexpr double kMaximumLightnessDiff = 0.35;

// This is the core definition of how ProfileThemeColors are obtained.
ProfileThemeColors GetProfileThemeColorsFromHighlightColor(
    SkColor highlight_color) {
  ProfileThemeColors colors;
  colors.profile_highlight_color = highlight_color;
  colors.default_avatar_fill_color = highlight_color;
  colors.default_avatar_stroke_color =
      GetAvatarStrokeColor(colors.default_avatar_fill_color);
  return colors;
}

size_t GenerateRandomIndex(size_t size) {
  DCHECK_GT(size, 0u);
  return static_cast<size_t>(base::RandInt(0, size - 1));
}

std::vector<int> GetAvailableColorIndices(
    const std::set<ProfileThemeColors>& used_theme_colors,
    absl::optional<double> current_color_lightness) {
  std::vector<int> available_color_indices;
  for (size_t i = 0; i < base::size(chrome_colors::kGeneratedColorsInfo); ++i) {
    ProfileThemeColors theme_colors =
        GetProfileThemeColorsForAutogeneratedColor(
            chrome_colors::kGeneratedColorsInfo[i].color);
    if (base::Contains(used_theme_colors, theme_colors))
      continue;

    const SkColor highlight = theme_colors.profile_highlight_color;
    if (!IsSaturatedForAutoselection(highlight))
      continue;
    if (current_color_lightness &&
        !IsLightForAutoselection(highlight, *current_color_lightness))
      continue;

    available_color_indices.push_back(i);
  }
  return available_color_indices;
}

double ExtractCurrentColorLightness(ProfileAttributesEntry* current_profile) {
  ProfileThemeColors current_colors;
  if (!current_profile) {
    current_colors = GetDefaultProfileThemeColors(
        ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors());
  } else {
    current_colors = current_profile->GetProfileThemeColors();
  }

  color_utils::HSL hsl;
  color_utils::SkColorToHSL(current_colors.profile_highlight_color, &hsl);
  return hsl.l;
}

}  // namespace

bool ProfileThemeColors::operator<(const ProfileThemeColors& other) const {
  return std::tie(this->profile_highlight_color,
                  this->default_avatar_fill_color,
                  this->default_avatar_stroke_color) <
         std::tie(other.profile_highlight_color,
                  other.default_avatar_fill_color,
                  other.default_avatar_stroke_color);
}

bool ProfileThemeColors::operator==(const ProfileThemeColors& other) const {
  return std::tie(this->profile_highlight_color,
                  this->default_avatar_fill_color,
                  this->default_avatar_stroke_color) ==
         std::tie(other.profile_highlight_color,
                  other.default_avatar_fill_color,
                  other.default_avatar_stroke_color);
}

bool ProfileThemeColors::operator!=(const ProfileThemeColors& other) const {
  return !(*this == other);
}

ProfileThemeColors GetProfileThemeColorsForThemeSupplier(
    const CustomThemeSupplier* supplier) {
  SkColor highlight_color;
  bool is_defined =
      supplier->GetColor(ThemeProperties::COLOR_FRAME_ACTIVE, &highlight_color);
  DCHECK(is_defined);
  return GetProfileThemeColorsFromHighlightColor(highlight_color);
}

ProfileThemeColors GetProfileThemeColorsForAutogeneratedColor(
    SkColor autogenerated_color) {
  auto pack = base::MakeRefCounted<BrowserThemePack>(
      CustomThemeSupplier::ThemeType::AUTOGENERATED);
  BrowserThemePack::BuildFromColor(autogenerated_color, pack.get());
  return GetProfileThemeColorsForThemeSupplier(pack.get());
}

ProfileThemeColors GetDefaultProfileThemeColors(bool dark_mode) {
  return GetProfileThemeColorsFromHighlightColor(
      ThemeProperties::GetDefaultColor(ThemeProperties::COLOR_FRAME_ACTIVE,
                                       /*incognito=*/false, dark_mode));
}

SkColor GetProfileForegroundTextColor(SkColor profile_highlight_color) {
  return color_utils::GetColorWithMaxContrast(profile_highlight_color);
}

SkColor GetProfileForegroundIconColor(SkColor profile_highlight_color) {
  SkColor text_color = GetProfileForegroundTextColor(profile_highlight_color);
  SkColor icon_color = color_utils::DeriveDefaultIconColor(text_color);
  return color_utils::BlendForMinContrast(icon_color, profile_highlight_color,
                                          text_color)
      .color;
}

SkColor GetAvatarStrokeColor(SkColor avatar_fill_color) {
  if (color_utils::IsDark(avatar_fill_color)) {
    return SK_ColorWHITE;
  }

  color_utils::HSL color_hsl;
  color_utils::SkColorToHSL(avatar_fill_color, &color_hsl);
  color_hsl.l = std::max(0., color_hsl.l - 0.5);
  return color_utils::HSLToSkColor(color_hsl, SkColorGetA(avatar_fill_color));
}

bool IsSaturatedForAutoselection(SkColor color) {
  color_utils::HSL hsl;
  color_utils::SkColorToHSL(color, &hsl);
  return hsl.s >= kMinimumSaturation;
}

bool IsLightForAutoselection(SkColor color, double reference_lightness) {
  color_utils::HSL hsl;
  color_utils::SkColorToHSL(color, &hsl);
  return std::abs(hsl.l - reference_lightness) <= kMaximumLightnessDiff;
}

chrome_colors::ColorInfo GenerateNewProfileColorWithGenerator(
    ProfileAttributesStorage& storage,
    base::OnceCallback<size_t(size_t count)> random_generator,
    ProfileAttributesEntry* current_profile) {
  // TODO(crbug.com/1108295): Return only a SkColor if the full ColorInfo is not
  // needed.
  std::set<ProfileThemeColors> used_theme_colors;
  for (ProfileAttributesEntry* entry : storage.GetAllProfilesAttributes()) {
    absl::optional<ProfileThemeColors> current_colors =
        entry->GetProfileThemeColorsIfSet();
    if (current_colors)
      used_theme_colors.insert(*current_colors);
  }

  double current_color_lightness =
      ExtractCurrentColorLightness(current_profile);

  // Collect indices of profile colors that match all the filters.
  std::vector<int> available_color_indices =
      GetAvailableColorIndices(used_theme_colors, current_color_lightness);
  // Relax the constraints until some colors become available.
  if (available_color_indices.empty()) {
    available_color_indices =
        GetAvailableColorIndices(used_theme_colors, absl::nullopt);
  }
  if (available_color_indices.empty()) {
    // If needed, we could allow unsaturated colors (shades of grey) before
    // allowing a duplicate color.
    available_color_indices =
        GetAvailableColorIndices(std::set<ProfileThemeColors>(), absl::nullopt);
  }
  DCHECK(!available_color_indices.empty());

  size_t size = available_color_indices.size();
  size_t available_index = std::move(random_generator).Run(size);
  DCHECK_LT(available_index, size);
  size_t index = available_color_indices[available_index];
  return chrome_colors::kGeneratedColorsInfo[index];
}

chrome_colors::ColorInfo GenerateNewProfileColor(
    ProfileAttributesEntry* current_profile) {
  return GenerateNewProfileColorWithGenerator(
      g_browser_process->profile_manager()->GetProfileAttributesStorage(),
      base::BindOnce(&GenerateRandomIndex), current_profile);
}
